查看原文
其他

CVE-2017-4901 VMware虚拟机逃逸漏洞分析【Frida Windows实例】

Imyang 看雪学院 2019-05-25


0x00 漏洞编号


修补时间编号
2017-03-14CVE-2017-4901



0x01 影响范围


产品版本级别修补版本
Workstation Pro12.x严重12.5.4
Fusion Pro<br>Fusion8.x严重8.5.5



0x02 漏洞简介


在VMware Workstation和Fusion中的拖放(Dnd)和复制粘贴(CP)功能存在堆溢出漏洞,这会让虚拟机客户端在宿主机上执行任意代码。



0x03 漏洞原理


1. RPCI通信机制

 

在介绍漏洞原理之前,先来了解一下VMWare中宿主机(host)与虚拟机(guest)之间的通信机制,其中的一种方式叫做Backdoor,利用windows的特权指令in,该指令在正常的host环境用户态中执行会报异常,而在guest中正是利用该异常被host的hypervisor捕获,来实现通信。


如下图,在vmtools.dll中的Message_Send函数调用Backdoor函数,Backdoor调用in eax,dx来实现通信。

 

 

guest中利用frida框架注入到vmtools.exe中使用RpcChannel_SendOneRaw发送消息。

 


gboolean

RpcChannel_SendOneRaw(const char *data,

                     size_t dataLen,

                     char **result,

                     size_t *resultLen)
;


RpcChannel_SendOneRaw调用路径:

RpcChannel_SendOneRaw -> RpcChannel_Send -> BkdoorChannelSend -> RpcOut_send -> Message_Send -> Backdoor -> in特权指令

 

RpcChannel_SendOneRaw的参数 const char *data就是给Host发送的指令, 在第3小节会讲host是怎么接收guest 的命令。

 

2. 漏洞

 

Dnd/CP的Version 4的dnd/cp分片数据包校验函数。

static Bool

DnDCPMsgV4IsPacketValid(const uint8 *packet,

                       size_t packetSize)

{

  DnDCPMsgHdrV4 *msgHdr = NULL;

  ASSERT(packet);



  if (packetSize < DND_CP_MSG_HEADERSIZE_V4) {

     return FALSE;

  }



  msgHdr = (DnDCPMsgHdrV4 *)packet;



  /* Payload size is not valid. */

  if (msgHdr->payloadSize > DND_CP_PACKET_MAX_PAYLOAD_SIZE_V4) {

     return FALSE;

  }



  /* Binary size is not valid. */

  if (msgHdr->binarySize > DND_CP_MSG_MAX_BINARY_SIZE_V4) {

     return FALSE;

  }



  /* Payload size is more than binary size. */

  if (msgHdr->payloadOffset + msgHdr->payloadSize > msgHdr->binarySize) {//[1]

     return FALSE;

  }



  return TRUE;

}



Bool

DnDCPMsgV4_UnserializeMultiple(DnDCPMsgV4 *msg,

                              const uint8 *packet,

                              size_t packetSize)

{

  DnDCPMsgHdrV4 *msgHdr = NULL;

  ASSERT(msg);

  ASSERT(packet);



  if (!DnDCPMsgV4IsPacketValid(packet, packetSize)) {

     return FALSE;

  }



  msgHdr = (DnDCPMsgHdrV4 *)packet;



  /*

   * For each session, there is at most 1 big message. If the received

   * sessionId is different with buffered one, the received packet is for

   * another another new message. Destroy old buffered message.

   */


  if (msg->binary &&

      msg->hdr.sessionId != msgHdr->sessionId) {

     DnDCPMsgV4_Destroy(msg);

  }



  /* Offset should be 0 for new message. */

  if (NULL == msg->binary && msgHdr->payloadOffset != 0) {

     return FALSE;

  }



  /* For existing buffered message, the payload offset should match. */

  if (msg->binary &&

      msg->hdr.sessionId == msgHdr->sessionId &&

      msg->hdr.payloadOffset != msgHdr->payloadOffset) {

     return FALSE;

  }



  if (NULL == msg->binary) {

     memcpy(msg, msgHdr, DND_CP_MSG_HEADERSIZE_V4);

     msg->binary = Util_SafeMalloc(msg->hdr.binarySize);

  }



  /* msg->hdr.payloadOffset is used as received binary size. */

  memcpy(msg->binary + msg->hdr.payloadOffset, //[2]

         packet + DND_CP_MSG_HEADERSIZE_V4,

         msgHdr->payloadSize);

  msg->hdr.payloadOffset += msgHdr->payloadSize;

  return TRUE;

}


open-vm-tools中的代码在此:dndCPMsgV4.c

 

对于Dnd/CP version 4的功能中,当guest发送分片Dnd/CP命令数据包时,会调用DnDCPMsgV4_UnserializeMultiple函数进行分片重组,重组的时候 DnDCPMsgV4IsPacketValid函数中的[1]处代码校验不严格,会导致[2]处堆溢出,可以构造如下的数据包来触发漏洞。

packet 1 {

   sessionId : 0x41414141

   binarySize: 0x100

   payloadOffset: 0

   payloadSize : 0x50

   ...

   //0x50 bytes binary

}

发送packet 1时,DnDCPMsgV4IsPacketValid函数的校验的不会有问题



packet 2 {

   sessionId : 0x41414141

   binarySize: 0x1000

   payloadOffset: 0x50

   payloadSize : 0x100

   ...

   //0x100 bytes binary

}

在发送后续的分片包时,DnDCPMsgV4IsPacketValid的校验就已经无效了,可以指定更大的binarySize来绕过校验


Dnd/CP的Version 3的dnd分片数据包校验函数。

Bool

DnD_TransportBufAppendPacket(DnDTransportBuffer *buf,          // IN/OUT

                            DnDTransportPacketHeader *packet, // IN

                            size_t packetSize)                // IN

{

  ASSERT(buf);

  ASSERT(packetSize == (packet->payloadSize + DND_TRANSPORT_PACKET_HEADER_SIZE) &&

         packetSize <= DND_MAX_TRANSPORT_PACKET_SIZE &&

         (packet->payloadSize + packet->offset) <= packet->totalSize &&

         packet->totalSize <= DNDMSG_MAX_ARGSZ);



  if (packetSize != (packet->payloadSize + DND_TRANSPORT_PACKET_HEADER_SIZE) ||

      packetSize > DND_MAX_TRANSPORT_PACKET_SIZE ||

      (packet->payloadSize + packet->offset) > packet->totalSize || //[3]

      packet->totalSize > DNDMSG_MAX_ARGSZ) {

     goto error;

  }



  /*

   * If seqNum does not match, it means either this is the first packet, or there

   * is a timeout in another side. Reset the buffer in all cases.

   */


  if (buf->seqNum != packet->seqNum) {

     DnD_TransportBufReset(buf);

  }



  if (!buf->buffer) {

     ASSERT(!packet->offset);

     if (packet->offset) {

        goto error;

     }

     buf->buffer = Util_SafeMalloc(packet->totalSize);

     buf->totalSize = packet->totalSize;

     buf->seqNum = packet->seqNum;

     buf->offset = 0;

  }



  if (buf->offset != packet->offset) {

     goto error;

  }



  memcpy(buf->buffer + buf->offset,

         packet->payload,

         packet->payloadSize);

  buf->offset += packet->payloadSize;

  return TRUE;



error:

  DnD_TransportBufReset(buf);

  return FALSE;

}


open-vm-tools中的代码在此:dndCommon.c

 

对于老版本Version 3的Dnd/CP的功能中,在[3]处同样对分片重组的包有着校验失效的问题,可以发送如下的数据包来触发溢出。

packet 1 {

   seqNum: 0x41414141

   totalSize: 0x100

   payloadSize : 0x50

   offset: 0

   ...

   //0x50 bytes buffer

}

发送packet 1时,DnD_TransportBufAppendPacket函数中[3]处不会有问题,



packet 2 {

   seqNum : 0x41414141

   totalSize: 0x1000

   payloadSize : 0x100

   offset: 0x50

   ...

   //0x100 bytes buffer

}

在发送后续的分片包时,DnD_TransportBufAppendPacke的校验就已经无效了,可以指定更大的totalSize来绕过校验



3. 漏洞原理

 

第2节看完open-vm-tools中漏洞溢出的地方,现在来看vmware-vmx.exe中怎么利用漏洞实现逃逸。

 

 

bind_function会把RPCI通信的命令和函数绑定到一个函数指针数组里面。


bind_function第3个参数是命令的名字,第4个参数是对应处理的回调函数。


回调函数的定义如下:


char fastcall handler(int64 a1, __int64 a2, const char request, int requestlen, const char **result, _DWORD resultlen);

 

handler第三个参数是接收的命令, 第5个参数是回复给guest的数据。

 

发送如下命令可以设置和查询dnd/cp的版本。

tools.capability.dnd_version 3            //设置dnd的版本为3

tools.capability.copypaste_version 3    //设置cp的版本为3

vmx.capability.dnd_version                //查询dnd的版本

vmx.capability.copypaste_version        //查询cp的版本


讲完host如何接收guest的命令后,来看看guest的堆溢出怎么触发host的逃逸。

 

在处理guest的“tools.capability.dnd_version 3"命令时,设置当前的dnd_version。



在处理guest的"vmx.capability.dnd_version"命令时,会获取当前的dnd_version,并且更新dnd/cp全局对象。



在Update_DndCP_Object函数中delete掉前一个版本的obj_CP和obj_Dnd, 析构对象的时候都会调用到他们各自的虚函数,只需要溢出到虚表上就能执行漏洞代码。




能够执行代码的路径很多,可以溢出Dnd,也可以溢出CP, 只需要选择溢出其中一个虚函数。


0x04 绕过ASLR


要想在guest中获取host的对象,就需要有个能够泄漏信息的地方,例如guest中发送info-set和info-get命令。

info-set guestinfo.KEY VALUE  //设置key/value键值对

info-get guestinfo.KEY          //获取key的值





利用info-set覆盖字符串的结尾NULL字符,让value字符串与后面的内存块连接起来,然后在info-get中读取Key,就能获取到value字符串后面的内存,达到信息泄漏。



0x05 Exploit分析


此次分析的exp是由unamer发布在github上的vmware_escape项目。

 

1. 设置DnD/CP版本

 


2. 绕过ASLR


多次发送info-set, info-get,进行堆溢出。





 

获取泄漏的信息,绕过ASLR。

 


3. 实现代码执行 


通过泄漏的信息,判断是指针是Dnd对象还是CP对象,然后设置不同的rop。






- End -


看雪ID:Imyang                              

https://bbs.pediy.com/user-548459.htm


本文由 Imyang 原创

转载请注明来自看雪社区



热门图书推荐:

 立即购买!



热门技术文章推荐:





公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存